/*
* Copyright (C) 1997-2001 Id Software, Inc.
* 
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
* 
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE.
* 
* See the GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307, USA.
*  
*/

// Created on 28.12.2003 by RST.
// $Id: GameTurret.java,v 1.7 2006/01/21 21:53:32 salomo Exp $
using System;
using Suake2.UI;
using Suake2.UI.client;
using M_Infantry = Suake2.UI.game.monsters.M_Infantry;
using Suake2.UI.qcommon;
using Suake2.UI.render;
using Suake2.UI.server;
using Lib = Suake2.UI.util.Lib;
using Math3D = Suake2.UI.util.Math3D;
namespace Suake2.UI.game
{
	
	public class GameTurret
	{
		public class AnonymousClassEntBlockedAdapter:EntBlockedAdapter
		{
			override public System.String ID
			{
				get
				{
					return "turret_blocked";
				}
				
			}
			public override void  blocked(edict_t self, edict_t other)
			{
				edict_t attacker;
				
				if (other.takedamage != 0)
				{
					if (self.teammaster.owner != null)
						attacker = self.teammaster.owner;
					else
						attacker = self.teammaster;
					GameCombat.T_Damage(other, self, attacker, Globals.vec3_origin, other.s.origin, Globals.vec3_origin, self.teammaster.dmg, 10, 0, Defines.MOD_CRUSH);
				}
			}
		}
		public class AnonymousClassEntThinkAdapter:EntThinkAdapter
		{
			override public System.String ID
			{
				get
				{
					return "turret_breach_think";
				}
				
			}
			public override bool think(edict_t self)
			{
				
				edict_t ent;
				float[] current_angles = new float[]{0, 0, 0};
				float[] delta = new float[]{0, 0, 0};
				
				Math3D.VectorCopy(self.s.angles, current_angles);
				jake2.game.GameTurret.AnglesNormalize(current_angles);
				
				jake2.game.GameTurret.AnglesNormalize(self.move_angles);
				if (self.move_angles[Defines.PITCH] > 180)
					self.move_angles[Defines.PITCH] -= 360;
				
				// clamp angles to mins & maxs
				if (self.move_angles[Defines.PITCH] > self.pos1[Defines.PITCH])
					self.move_angles[Defines.PITCH] = self.pos1[Defines.PITCH];
				else if (self.move_angles[Defines.PITCH] < self.pos2[Defines.PITCH])
					self.move_angles[Defines.PITCH] = self.pos2[Defines.PITCH];
				
				if ((self.move_angles[Defines.YAW] < self.pos1[Defines.YAW]) || (self.move_angles[Defines.YAW] > self.pos2[Defines.YAW]))
				{
					float dmin, dmax;
					
					dmin = System.Math.Abs(self.pos1[Defines.YAW] - self.move_angles[Defines.YAW]);
					if (dmin < - 180)
						dmin += 360;
					else if (dmin > 180)
						dmin -= 360;
					dmax = System.Math.Abs(self.pos2[Defines.YAW] - self.move_angles[Defines.YAW]);
					if (dmax < - 180)
						dmax += 360;
					else if (dmax > 180)
						dmax -= 360;
					if (System.Math.Abs(dmin) < System.Math.Abs(dmax))
						self.move_angles[Defines.YAW] = self.pos1[Defines.YAW];
					else
						self.move_angles[Defines.YAW] = self.pos2[Defines.YAW];
				}
				
				Math3D.VectorSubtract(self.move_angles, current_angles, delta);
				if (delta[0] < - 180)
					delta[0] += 360;
				else if (delta[0] > 180)
					delta[0] -= 360;
				if (delta[1] < - 180)
					delta[1] += 360;
				else if (delta[1] > 180)
					delta[1] -= 360;
				delta[2] = 0;
				
				if (delta[0] > self.speed * Defines.FRAMETIME)
					delta[0] = self.speed * Defines.FRAMETIME;
				if (delta[0] < (- 1) * self.speed * Defines.FRAMETIME)
					delta[0] = (- 1) * self.speed * Defines.FRAMETIME;
				if (delta[1] > self.speed * Defines.FRAMETIME)
					delta[1] = self.speed * Defines.FRAMETIME;
				if (delta[1] < (- 1) * self.speed * Defines.FRAMETIME)
					delta[1] = (- 1) * self.speed * Defines.FRAMETIME;
				
				Math3D.VectorScale(delta, 1.0f / Defines.FRAMETIME, self.avelocity);
				
				self.nextthink = GameBase.level.time + Defines.FRAMETIME;
				
				for (ent = self.teammaster; ent != null; ent = ent.teamchain)
					ent.avelocity[1] = self.avelocity[1];
				
				// if we have adriver, adjust his velocities
				if (self.owner != null)
				{
					float angle;
					float target_z;
					float diff;
					float[] target = new float[]{0, 0, 0};
					float[] dir = new float[]{0, 0, 0};
					
					// angular is easy, just copy ours
					self.owner.avelocity[0] = self.avelocity[0];
					self.owner.avelocity[1] = self.avelocity[1];
					
					// x & y
					angle = self.s.angles[1] + self.owner.move_origin[1];
					angle = (float) (angle * (System.Math.PI * 2 / 360));
					//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
					target[0] = GameTurret.SnapToEights((float) (self.s.origin[0] + System.Math.Cos(angle) * self.owner.move_origin[0]));
					//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
					target[1] = GameTurret.SnapToEights((float) (self.s.origin[1] + System.Math.Sin(angle) * self.owner.move_origin[0]));
					target[2] = self.owner.s.origin[2];
					
					Math3D.VectorSubtract(target, self.owner.s.origin, dir);
					self.owner.velocity[0] = dir[0] * 1.0f / Defines.FRAMETIME;
					self.owner.velocity[1] = dir[1] * 1.0f / Defines.FRAMETIME;
					
					// z
					//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
					angle = self.s.angles[Defines.PITCH] * (float) (System.Math.PI * 2f / 360f);
					//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
					target_z = GameTurret.SnapToEights((float) (self.s.origin[2] + self.owner.move_origin[0] * System.Math.Tan(angle) + self.owner.move_origin[2]));
					
					diff = target_z - self.owner.s.origin[2];
					self.owner.velocity[2] = diff * 1.0f / Defines.FRAMETIME;
					
					if ((self.spawnflags & 65536) != 0)
					{
						jake2.game.GameTurret.turret_breach_fire(self);
						self.spawnflags &= ~ 65536;
					}
				}
				return true;
			}
		}
		public class AnonymousClassEntThinkAdapter1:EntThinkAdapter
		{
			override public System.String ID
			{
				get
				{
					return "turret_breach_finish_init";
				}
				
			}
			public override bool think(edict_t self)
			{
				
				// get and save info for muzzle location
				if (self.target == null)
				{
					GameBase.gi.dprintf(self.classname + " at " + Lib.vtos(self.s.origin) + " needs a target\n");
				}
				else
				{
					self.target_ent = GameBase.G_PickTarget(self.target);
					Math3D.VectorSubtract(self.target_ent.s.origin, self.s.origin, self.move_origin);
					GameUtil.G_FreeEdict(self.target_ent);
				}
				
				self.teammaster.dmg = self.dmg;
				self.think = Suake2.UI.game.GameTurret.turret_breach_think;
				self.think.think(self);
				return true;
			}
		}
		public class AnonymousClassEntDieAdapter:EntDieAdapter
		{
			override public System.String ID
			{
				get
				{
					return "turret_driver_die";
				}
				
			}
			public override void  die(edict_t self, edict_t inflictor, edict_t attacker, int damage, float[] point)
			{
				
				edict_t ent;
				
				// level the gun
				self.target_ent.move_angles[0] = 0;
				
				// remove the driver from the end of them team chain
				for (ent = self.target_ent.teammaster; ent.teamchain != self; ent = ent.teamchain)
					;
				ent.teamchain = null;
				self.teammaster = null;
				self.flags &= ~ Defines.FL_TEAMSLAVE;
				
				self.target_ent.owner = null;
				self.target_ent.teammaster.owner = null;
				
				M_Infantry.infantry_die.die(self, inflictor, attacker, damage, null);
			}
		}
		public class AnonymousClassEntThinkAdapter2:EntThinkAdapter
		{
			override public System.String ID
			{
				get
				{
					return "turret_driver_think";
				}
				
			}
			public override bool think(edict_t self)
			{
				
				float[] target = new float[]{0, 0, 0};
				float[] dir = new float[]{0, 0, 0};
				float reaction_time;
				
				self.nextthink = GameBase.level.time + Defines.FRAMETIME;
				
				if (self.enemy != null && (!self.enemy.inuse || self.enemy.health <= 0))
					self.enemy = null;
				
				if (null == self.enemy)
				{
					if (!GameUtil.FindTarget(self))
						return true;
					self.monsterinfo.trail_time = GameBase.level.time;
					self.monsterinfo.aiflags &= ~ Defines.AI_LOST_SIGHT;
				}
				else
				{
					if (GameUtil.visible(self, self.enemy))
					{
						if ((self.monsterinfo.aiflags & Defines.AI_LOST_SIGHT) != 0)
						{
							self.monsterinfo.trail_time = GameBase.level.time;
							self.monsterinfo.aiflags &= ~ Defines.AI_LOST_SIGHT;
						}
					}
					else
					{
						self.monsterinfo.aiflags |= Defines.AI_LOST_SIGHT;
						return true;
					}
				}
				
				// let the turret know where we want it to aim
				Math3D.VectorCopy(self.enemy.s.origin, target);
				target[2] += self.enemy.viewheight;
				Math3D.VectorSubtract(target, self.target_ent.s.origin, dir);
				Math3D.vectoangles(dir, self.target_ent.move_angles);
				
				// decide if we should shoot
				if (GameBase.level.time < self.monsterinfo.attack_finished)
					return true;
				
				reaction_time = (3 - GameBase.skill.value_Renamed) * 1.0f;
				if ((GameBase.level.time - self.monsterinfo.trail_time) < reaction_time)
					return true;
				
				self.monsterinfo.attack_finished = GameBase.level.time + reaction_time + 1.0f;
				//FIXME how do we really want to pass this along?
				self.target_ent.spawnflags |= 65536;
				return true;
			}
		}
		public class AnonymousClassEntThinkAdapter3:EntThinkAdapter
		{
			override public System.String ID
			{
				get
				{
					return "turret_driver_link";
				}
				
			}
			public override bool think(edict_t self)
			{
				
				float[] vec = new float[]{0, 0, 0};
				edict_t ent;
				
				self.think = Suake2.UI.game.GameTurret.turret_driver_think;
				self.nextthink = GameBase.level.time + Defines.FRAMETIME;
				
				self.target_ent = GameBase.G_PickTarget(self.target);
				self.target_ent.owner = self;
				self.target_ent.teammaster.owner = self;
				Math3D.VectorCopy(self.target_ent.s.angles, self.s.angles);
				
				vec[0] = self.target_ent.s.origin[0] - self.s.origin[0];
				vec[1] = self.target_ent.s.origin[1] - self.s.origin[1];
				vec[2] = 0;
				self.move_origin[0] = Math3D.VectorLength(vec);
				
				Math3D.VectorSubtract(self.s.origin, self.target_ent.s.origin, vec);
				Math3D.vectoangles(vec, vec);
				jake2.game.GameTurret.AnglesNormalize(vec);
				
				self.move_origin[1] = vec[1];
				self.move_origin[2] = self.s.origin[2] - self.target_ent.s.origin[2];
				
				// add the driver to the end of them team chain
				for (ent = self.target_ent.teammaster; ent.teamchain != null; ent = ent.teamchain)
					;
				ent.teamchain = self;
				self.teammaster = self.target_ent.teammaster;
				self.flags |= Defines.FL_TEAMSLAVE;
				return true;
			}
		}
		
		public static void  AnglesNormalize(float[] vec)
		{
			while (vec[0] > 360)
				vec[0] -= 360;
			while (vec[0] < 0)
				vec[0] += 360;
			while (vec[1] > 360)
				vec[1] -= 360;
			while (vec[1] < 0)
				vec[1] += 360;
		}
		
		public static float SnapToEights(float x)
		{
			x = (float) (x * 8.0);
			if (x > 0.0)
				x = (float) (x + 0.5);
			else
				x = (float) (x - 0.5);
			//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
			return 0.125f * (int) x;
		}
		
		/// <summary> QUAKED turret_breach (0 0 0) ? This portion of the turret can change both
		/// pitch and yaw. The model should be made with a flat pitch. It (and the
		/// associated base) need to be oriented towards 0. Use "angle" to set the
		/// starting angle.
		/// 
		/// "speed" default 50 "dmg" default 10 "angle" point this forward "target"
		/// point this at an info_notnull at the muzzle tip "minpitch" min acceptable
		/// pitch angle : default -30 "maxpitch" max acceptable pitch angle : default
		/// 30 "minyaw" min acceptable yaw angle : default 0 "maxyaw" max acceptable
		/// yaw angle : default 360
		/// </summary>
		
		public static void  turret_breach_fire(edict_t self)
		{
			float[] f = new float[]{0, 0, 0}, r = new float[]{0, 0, 0}, u = new float[]{0, 0, 0};
			float[] start = new float[]{0, 0, 0};
			int damage;
			int speed;
			
			Math3D.AngleVectors(self.s.angles, f, r, u);
			Math3D.VectorMA(self.s.origin, self.move_origin[0], f, start);
			Math3D.VectorMA(start, self.move_origin[1], r, start);
			Math3D.VectorMA(start, self.move_origin[2], u, start);
			
			//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
			damage = (int) (100 + Lib.random() * 50);
			//UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"
			speed = (int) (550 + 50 * GameBase.skill.value_Renamed);
			GameWeapon.fire_rocket(self.teammaster.owner, start, f, damage, speed, 150, damage);
			GameBase.gi.positioned_sound(start, self, Defines.CHAN_WEAPON, GameBase.gi.soundindex("weapons/rocklf1a.wav"), 1, Defines.ATTN_NORM, 0);
		}
		
		public static void  SP_turret_breach(edict_t self)
		{
			self.solid = Defines.SOLID_BSP;
			self.movetype = Defines.MOVETYPE_PUSH;
			GameBase.gi.setmodel(self, self.model);
			
			if (self.speed == 0)
				self.speed = 50;
			if (self.dmg == 0)
				self.dmg = 10;
			
			if (GameBase.st.minpitch == 0)
				GameBase.st.minpitch = - 30;
			if (GameBase.st.maxpitch == 0)
				GameBase.st.maxpitch = 30;
			if (GameBase.st.maxyaw == 0)
				GameBase.st.maxyaw = 360;
			
			self.pos1[Defines.PITCH] = (- 1) * GameBase.st.minpitch;
			self.pos1[Defines.YAW] = GameBase.st.minyaw;
			self.pos2[Defines.PITCH] = (- 1) * GameBase.st.maxpitch;
			self.pos2[Defines.YAW] = GameBase.st.maxyaw;
			
			self.ideal_yaw = self.s.angles[Defines.YAW];
			self.move_angles[Defines.YAW] = self.ideal_yaw;
			
			self.blocked = turret_blocked;
			
			self.think = turret_breach_finish_init;
			self.nextthink = GameBase.level.time + Defines.FRAMETIME;
			GameBase.gi.linkentity(self);
		}
		
		/// <summary> QUAKED turret_base (0 0 0) ? This portion of the turret changes yaw only.
		/// MUST be teamed with a turret_breach.
		/// </summary>
		
		public static void  SP_turret_base(edict_t self)
		{
			self.solid = Defines.SOLID_BSP;
			self.movetype = Defines.MOVETYPE_PUSH;
			GameBase.gi.setmodel(self, self.model);
			self.blocked = turret_blocked;
			GameBase.gi.linkentity(self);
		}
		
		public static void  SP_turret_driver(edict_t self)
		{
			if (GameBase.deathmatch.value_Renamed != 0)
			{
				GameUtil.G_FreeEdict(self);
				return ;
			}
			
			self.movetype = Defines.MOVETYPE_PUSH;
			self.solid = Defines.SOLID_BBOX;
			self.s.modelindex = GameBase.gi.modelindex("models/monsters/infantry/tris.md2");
			Math3D.VectorSet(self.mins, - 16, - 16, - 24);
			Math3D.VectorSet(self.maxs, 16, 16, 32);
			
			self.health = 100;
			self.gib_health = 0;
			self.mass = 200;
			self.viewheight = 24;
			
			self.die = turret_driver_die;
			self.monsterinfo.stand = M_Infantry.infantry_stand;
			
			self.flags |= Defines.FL_NO_KNOCKBACK;
			
			GameBase.level.total_monsters++;
			
			self.svflags |= Defines.SVF_MONSTER;
			self.s.renderfx |= Defines.RF_FRAMELERP;
			self.takedamage = Defines.DAMAGE_AIM;
			self.use = GameUtil.monster_use;
			self.clipmask = Defines.MASK_MONSTERSOLID;
			Math3D.VectorCopy(self.s.origin, self.s.old_origin);
			self.monsterinfo.aiflags |= Defines.AI_STAND_GROUND | Defines.AI_DUCKED;
			
			if (GameBase.st.item != null)
			{
				self.item = GameItems.FindItemByClassname(GameBase.st.item);
				if (self.item == null)
					GameBase.gi.dprintf(self.classname + " at " + Lib.vtos(self.s.origin) + " has bad item: " + GameBase.st.item + "\n");
			}
			
			self.think = turret_driver_link;
			self.nextthink = GameBase.level.time + Defines.FRAMETIME;
			
			GameBase.gi.linkentity(self);
		}
		
		//UPGRADE_NOTE: The initialization of  'turret_blocked' was moved to static method 'jake2.game.GameTurret'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		internal static EntBlockedAdapter turret_blocked;
		
		//UPGRADE_NOTE: The initialization of  'turret_breach_think' was moved to static method 'jake2.game.GameTurret'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		internal static EntThinkAdapter turret_breach_think;
		
		//UPGRADE_NOTE: The initialization of  'turret_breach_finish_init' was moved to static method 'jake2.game.GameTurret'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		internal static EntThinkAdapter turret_breach_finish_init;
		
		/*
		* QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) Must NOT be on the
		* team with the rest of the turret parts. Instead it must target the
		* turret_breach.
		*/
		//UPGRADE_NOTE: The initialization of  'turret_driver_die' was moved to static method 'jake2.game.GameTurret'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		internal static EntDieAdapter turret_driver_die;
		
		//UPGRADE_NOTE: The initialization of  'turret_driver_think' was moved to static method 'jake2.game.GameTurret'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		internal static EntThinkAdapter turret_driver_think;
		
		//UPGRADE_NOTE: The initialization of  'turret_driver_link' was moved to static method 'jake2.game.GameTurret'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		public static EntThinkAdapter turret_driver_link;
		static GameTurret()
		{
			turret_blocked = new AnonymousClassEntBlockedAdapter();
			turret_breach_think = new AnonymousClassEntThinkAdapter();
			turret_breach_finish_init = new AnonymousClassEntThinkAdapter1();
			turret_driver_die = new AnonymousClassEntDieAdapter();
			turret_driver_think = new AnonymousClassEntThinkAdapter2();
			turret_driver_link = new AnonymousClassEntThinkAdapter3();
		}
	}
}